package com.hjt.gulimall.product.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hjt.gulimall.product.service.CategoryBrandRelationService;
import com.hjt.gulimall.product.vo.Catelog2Vo;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hjt.common.utils.PageUtils;
import com.hjt.common.utils.Query;

import com.hjt.gulimall.product.dao.CategoryDao;
import com.hjt.gulimall.product.entity.CategoryEntity;
import com.hjt.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService relationService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    RedissonClient redisson;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 查出所有商品分类的父子树形结构
     * @return
     */
    @Override
    public List<CategoryEntity> listWithTree() {

        // 1.查出所有分类
        List<CategoryEntity> categoryEntityList = baseMapper.selectList(null);
        // 2.组装父子结构
        // 找出所有以及分类
        List<CategoryEntity> oneCategories = categoryEntityList.stream()
            .filter(category -> category.getCatLevel() == 1)
            .peek(category ->{
                // 找到二级分类
                List<CategoryEntity> twoCategories = getChildren(category, categoryEntityList).stream()
                    .peek(twoCategory -> twoCategory.setChildren(getChildren(twoCategory,categoryEntityList))) // 设置三级分类
                    .collect(Collectors.toList());
                category.setChildren(twoCategories);
            })
            .sorted(Comparator.comparingInt(CategoryEntity::getSort)) // 根据sort字段排序
            .collect(Collectors.toList());
        return oneCategories;
    }

    /**
     * 获取某个分类的子分类
     * @param categoryEntity
     * @param categories
     * @return
     */
    private List<CategoryEntity> getChildren(CategoryEntity categoryEntity, List<CategoryEntity> categories) {
        return categories.stream()
                .filter(category -> category.getParentCid().equals(categoryEntity.getCatId()))
                .sorted(Comparator.comparingInt(CategoryEntity::getSort)) // 根据sort字段排序
                .collect(Collectors.toList());
    }

    /**
     * 批量删除分类，删除的时候检查是否有被引用
     * @param idList
     */
    @Override
    public void removeMenuByIds(List<Long> idList) {
        // TODO:删除时检查是否有被引用

        baseMapper.deleteBatchIds(idList);

    }

    /**
     * 找到分类的完整路径
     * @param catelogId
     * @return
     */
    @Override
    public Long[] findCatelogPath(Long catelogId) {
        LinkedList<Long> paths = new LinkedList<>();

        // 根据父id依次往上查找
        CategoryEntity entity = this.getById(catelogId);
        while (entity != null) {
            paths.addFirst(entity.getCatId());
            entity = this.getById(entity.getParentCid());
        }
        return paths.toArray(new Long[paths.size()]);
    }


    // 级联更新，更新其他关联表的冗余数据
    // 更新时，将有关的缓存数据失效
//    @Caching(evict = {
//            @CacheEvict(value={"category"},key="'firstLevelCategories'"),
//            @CacheEvict(value = {"category"},key = "'catelogJson'")
//    })
    @CacheEvict(value = {"category"}, allEntries = true) // allEntries=true，将分区的所有缓存都删除
    @Transactional
    @Override
    public void updateCascade(CategoryEntity category) {
        baseMapper.updateById(category);
        Long catId = category.getCatId();
        String name = category.getName();
        relationService.updateCategory(catId, name);
    }

    // 查询所有的一级分类
    @Cacheable(value={"category"},key="'firstLevelCategories'")
    @Override
    public List<CategoryEntity> getFirstLevelCategories() {
        return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
    }


    // 对于每一个需要缓存的数据，可以指定存入缓存的名字。建议按照业务类型分区
    // 当缓存中有数据时，方法不会执行，返回缓存中的数据。当缓存中没有数据时，执行方法，并讲方法的返回结果存入缓存中
    @Cacheable(value = {"category"},key = "'catelogJson'")
    @Override
    public Map<String, List<Catelog2Vo>> getCatelogJson() {
        Map<String, List<Catelog2Vo>> map = new HashMap<>();
        // 1.1查出所有分类
        List<CategoryEntity> categoryEntityList = this.list();
        // 1.1查出所有一级分类
        List<CategoryEntity> firstLevelCategories = this.filterByParentCid(categoryEntityList, 0L);
        // 添加数据
        firstLevelCategories.forEach(category1 -> {
            Long category1Id = category1.getCatId();
            // 2.查出一级分类下的二级分类并封装数据
            List<CategoryEntity> category2List = this.filterByParentCid(categoryEntityList, category1Id);
            if (category2List != null) {
                List<Catelog2Vo> catelog2VoList = category2List.stream().map(category2 -> {
                    Long category2Id = category2.getCatId();
                    Catelog2Vo catelog2Vo = new Catelog2Vo();
                    catelog2Vo.setId(category2.getCatId().toString());
                    catelog2Vo.setName(category2.getName());
                    catelog2Vo.setCatalog1Id(category1Id.toString());
                    // 3.查出二级分类下的三级分类并封装数据
                    List<CategoryEntity> category3List = this.filterByParentCid(categoryEntityList, category2Id);
                    List<Catelog2Vo.Catelog3Vo> catelog3VoList = category3List.stream().map(category3 -> {
                        Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo();
                        catelog3Vo.setId(category3.getCatId().toString());
                        catelog3Vo.setName(category3.getName());
                        catelog3Vo.setCatalog2Id(category2Id.toString());
                        return catelog3Vo;
                    }).collect(Collectors.toList());
                    // 设置三级分类列表
                    catelog2Vo.setCatalog3List(catelog3VoList);
                    return catelog2Vo;
                }).collect(Collectors.toList());
                // 放入到map中
                map.put(category1Id.toString(), catelog2VoList);
            }
        });
        return map;
    }


    // 使用分布式锁，解决缓存问题
    public Map<String, List<Catelog2Vo>> getCatelogJson2() {
        // 获取分布式锁
        RLock lock = redisson.getLock("catelogJson-lock");
        // 加锁
        lock.lock(10,TimeUnit.SECONDS);
        Map<String, List<Catelog2Vo>> map = null;
        try {
            map = getCatelogJsonData();
        } finally {
            // 释放锁
            lock.unlock();
        }
        return map;
    }

    // 使用redis的setnx 加锁，解决缓存问题
    public Map<String, List<Catelog2Vo>> getCatelogJsonWithRedisLock() {
        /**
         * 缓存存在的问题
         * 1.缓存穿透：大量访问完全不存在的数据，缓存中没有，就会大量请求访问数据库。将不存在的数据也缓存起来，设置过期时间
         * 2.缓存雪崩：缓存中的大量数据同时过期，访问这些数据会造成直接访问数据库，造成雪崩。解决：设置过期时间时+随机值
         * 3.缓存击穿：大量请求同时访问一个数据，一旦该数据失效，就会有大量请求直接穿过缓存访问数据库。解决：加锁
         */
        // 加入缓存逻辑
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        // 获取redis分布式锁（保证加锁的原子性）
        String uuid = UUID.randomUUID().toString(); // 锁的值，保证释放锁时，释放的是自己的锁
        Boolean lock = ops.setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        // 获取到锁
        if (lock != null && lock) {
            System.out.println("成功获取锁");
            Map<String, List<Catelog2Vo>> catelogJsonObject = null;
            try {
                catelogJsonObject =  getCatelogJsonData();
            }
            finally {
                // 通过lua脚本，释放锁，做到原子性
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                redisTemplate.execute(new DefaultRedisScript<Integer>(script, Integer.class), Arrays.asList("lock"), uuid);
            }
            return catelogJsonObject;
        } else {
            // 尝试重新获取锁
            System.out.println("等待重新获取锁");
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return getCatelogJson();
        }
    }


    // 缓存中有则从缓存中获取，没有则查询数据库
    public Map<String, List<Catelog2Vo>> getCatelogJsonData() {
        // 加入缓存逻辑
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String catelogJson = ops.get("catelogJson");
        ObjectMapper omp = new ObjectMapper();

        // 已缓存
        if (StringUtils.hasLength(catelogJson)) {
            // 从redis中读取
            try {
                return omp.readValue(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {});
            } catch (IOException ioException) {
                // 解析失败，从数据库中查
                ioException.printStackTrace();
                return getCatelogJsonFromDb();
            }
        }
        else {
            synchronized (this) {
                // 没有缓存，则从数据库中查询
                Map<String, List<Catelog2Vo>> catelogJsonObject = getCatelogJsonFromDb();
                try {
                    // 保存到缓存中
                    catelogJson = omp.writeValueAsString(catelogJsonObject);
                    ops.set("catelogJson", catelogJson, 1, TimeUnit.DAYS);
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
                return catelogJsonObject;
            }
        }
    }

    // 从数据库中获取数据
    private Map<String, List<Catelog2Vo>> getCatelogJsonFromDb() {
        Map<String, List<Catelog2Vo>> map = new HashMap<>();
        // 1.1查出所有分类
        List<CategoryEntity> categoryEntityList = this.list();
        // 1.1查出所有一级分类
        List<CategoryEntity> firstLevelCategories = this.filterByParentCid(categoryEntityList, 0L);
        // 添加数据
        firstLevelCategories.forEach(category1 -> {
            Long category1Id = category1.getCatId();
            // 2.查出一级分类下的二级分类并封装数据
            List<CategoryEntity> category2List = this.filterByParentCid(categoryEntityList, category1Id);
            if (category2List != null) {
                List<Catelog2Vo> catelog2VoList = category2List.stream().map(category2 -> {
                    Long category2Id = category2.getCatId();
                    Catelog2Vo catelog2Vo = new Catelog2Vo();
                    catelog2Vo.setId(category2.getCatId().toString());
                    catelog2Vo.setName(category2.getName());
                    catelog2Vo.setCatalog1Id(category1Id.toString());
                    // 3.查出二级分类下的三级分类并封装数据
                    List<CategoryEntity> category3List = this.filterByParentCid(categoryEntityList, category2Id);
                    List<Catelog2Vo.Catelog3Vo> catelog3VoList = category3List.stream().map(category3 -> {
                        Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo();
                        catelog3Vo.setId(category3.getCatId().toString());
                        catelog3Vo.setName(category3.getName());
                        catelog3Vo.setCatalog2Id(category2Id.toString());
                        return catelog3Vo;
                    }).collect(Collectors.toList());
                    // 设置三级分类列表
                    catelog2Vo.setCatalog3List(catelog3VoList);
                    return catelog2Vo;
                }).collect(Collectors.toList());
                // 放入到map中
                map.put(category1Id.toString(), catelog2VoList);
            }
        });
        return map;
    }


    // 从分类列表中获取某个分类的子分类
    private List<CategoryEntity> filterByParentCid(List<CategoryEntity> categoryEntityList, Long parentCid) {
       return categoryEntityList.stream().filter(categoryEntity -> categoryEntity.getParentCid().equals(parentCid)).collect(Collectors.toList());
    }
}